package coderx

import (
	"errors"
	"gitee.com/zhongguo168a/gocodes/datax/jsonmap"
	"gitee.com/zhongguo168a/gocodes/datax/reflectx"
	"gitee.com/zhongguo168a/gocodes/datax/schemax"
)

func NewRefToMap() (obj *RefToMap) {
	obj = &RefToMap{
		replaceStringToAlias: map[string]string{},
	}
	return
}

type RefToMap struct {
	// 忽视空值
	// 空值不会输出到map中
	ignoreNil bool
	// _type字符串使用枚举 schemax.SCHEMA_TYPE 代替
	replaceMapTypeToSchemaEnum bool
	// 字符串使用别名代替
	replaceStringToAlias map[string]string
	// map存在的键值才会输出
	// map中
	exportByMapKey bool
	//
	source reflectx.IRefObject
}

func (coder *RefToMap) Reset() {
	coder.source = nil
	coder.ignoreNil = false
	coder.replaceStringToAlias = map[string]string{}
	coder.exportByMapKey = false
}

func (coder *RefToMap) SetSource(source reflectx.IRefObject) *RefToMap {
	coder.source = source
	return coder
}

func (coder *RefToMap) ReplaceMapTypeToSchemaEnum() *RefToMap {
	coder.replaceMapTypeToSchemaEnum = true
	return coder
}

func (coder *RefToMap) ReplaceStringToAlias(from, to string) *RefToMap {
	coder.replaceStringToAlias[from] = to
	return coder
}

func (coder *RefToMap) ExportByMapKey() *RefToMap {
	coder.exportByMapKey = true
	return coder
}

func (coder *RefToMap) Create() (r map[string]interface{}, err error) {
	r = map[string]interface{}{}
	err = coder.Write(r)
	return
}

func (coder *RefToMap) Write(target map[string]interface{}) (err error) {
	if target == nil {
		err = errors.New("target is nil")
		return
	}

	source := coder.source
	description := schemax.GetDeclByKey(source.RefType())
	if description == nil {
		err = errors.New("description not found: " + source.RefType())
		return
	}
	reftype := reflectx.GetType(source.RefType())
	if reftype == nil {
		err = errors.New("reftype not found: " + source.RefType())
		return
	}
	err = coder.toMapObj(reftype, source, target, description, 1)
	return
}

// keyMode 0-source的字段, 1-所有字段
func (coder *RefToMap) toMapObj(reftype reflectx.IRefType, obj reflectx.IRefObject, smap map[string]interface{}, description schemax.IDecl, exportAllKey int) (err error) {
	decl := description.(*schemax.ClassDecl)
	var (
		keyModeChild = 1 // 默认子属性默认遍历所有字段
	)

	// 注：如果采用遍历source的方式，因为需要取field，所以还是需要对decl.Fields进行遍历，遍历次数可能更多
	allfields := decl.GetAllField()
	for _, field := range allfields {
		fname := field.Name
		alias := field.Alias()

		mval, mhas := smap[alias]
		if coder.exportByMapKey { // 需要指定字段
			if !mhas { // 没有字段，跳过
				continue
			}
			if mval == 1 { // 需要处理
				keyModeChild = 1
				mval = nil // 设置为nil
			} else {
				keyModeChild = exportAllKey
			}
		}
		switch ftyp := field.Type.(type) {
		case *schemax.ClassType:
			st, isNil := reftype.(reflectx.IRefField).RefGet(obj, field.Name)
			if isNil {
				continue
			}
			rval, classerr := coder.toMapClass(st.(reflectx.IRefObject), mval, keyModeChild)
			if classerr != nil {
				err = classerr
				return
			}
			if rval == nil {
				continue
			}

			smap[alias] = rval
		case *schemax.ArrayType:
			rval, sliceerr := coder.toMapSlice(reftype.(reflectx.IRefSlice), obj, fname, ftyp, keyModeChild)
			if sliceerr != nil {
				err = sliceerr
				return
			}
			if rval == nil {
				continue
			}
			smap[alias] = rval
		case *schemax.MapType:
			rmap, maperr := coder.toMapMap(reftype.(reflectx.IRefMap), obj, mval, fname, ftyp, keyModeChild)
			if maperr != nil {
				err = maperr
				return
			}
			if rmap == nil {
				continue
			}
			smap[alias] = rmap
		case *schemax.EnumType:
			smap[alias], _ = reftype.(reflectx.IRefField).RefGet(obj, field.Name)
		case *schemax.BasicType:
			oval, _ := reftype.(reflectx.IRefField).RefGet(obj, field.Name)
			smap[alias] = coder.toMapConvert(ftyp, oval)
		case *schemax.AnyType:
			st, isNil := reftype.(reflectx.IRefField).RefGet(obj, field.Name)
			if isNil {
				continue
			}

			rmap, anyerr := coder.toMapAny(st, mval, keyModeChild)
			if anyerr != nil {
				err = anyerr
				return
			}
			if rmap == nil {
				continue
			}
			smap[alias] = rmap
		default:
		}
	}

	return
}

func (coder *RefToMap) toMapFunc(ist interface{}, mval interface{}, keyMode int) (interface{}, error) {
	st, ok := ist.(reflectx.IRefObject)
	if ok {
		rval, classerr := coder.toMapClass(st, mval, keyMode)
		if classerr != nil {
			return nil, classerr
		}
		if rval == nil {
			return nil, nil
		}
		return rval, nil
	} else {
		return ist, nil
	}
}

func (coder *RefToMap) toMapAny(ist interface{}, mval interface{}, keyMode int) (interface{}, error) {
	st, ok := ist.(reflectx.IRefObject)
	if ok {
		rval, classerr := coder.toMapClass(st, mval, keyMode)
		if classerr != nil {
			return nil, classerr
		}
		if rval == nil {
			return nil, nil
		}
		rval["_type"] = st.RefType()
		return rval, nil
	} else {
		return ist, nil
	}
}

func (coder *RefToMap) toMapConvert(basic *schemax.BasicType, val interface{}) (r interface{}) {
	return jsonmap.ConvertBasicToJsonValue(basic.Kind, val)
}

func (coder *RefToMap) toMapClass(st reflectx.IRefObject, sany interface{}, keyMode int) (m map[string]interface{}, err error) {
	if st == nil {
		return
	}
	stdesc := schemax.GetDeclByKey(st.RefType())
	if stdesc == nil {
		err = errors.New("map description not found: " + st.RefType())
		return
	}
	reftype := reflectx.GetType(st.RefType())
	if reftype == nil {
		err = errors.New("map reftype not found: " + st.RefType())
		return
	}

	if sany == nil {
		m = map[string]interface{}{}
	} else {
		m = sany.(map[string]interface{})
	}
	err = coder.toMapObj(reftype, st, m, stdesc, keyMode)
	return
}

func (coder *RefToMap) toMapSlice(reftype reflectx.IRefSlice, obj reflectx.IRefObject, fname string, ftyp *schemax.ArrayType, keyMode int) (marr []interface{}, err error) {
	objlen := reftype.RefSliceLength(obj, fname)
	if objlen == 0 {
		return
	}
	marr = make([]interface{}, objlen, objlen)
	//
	for i := 0; i < objlen; i++ {
		switch etyp := ftyp.Elem.(type) {
		case *schemax.ClassType:
			st, _ := reftype.RefSliceGet(obj, fname, i)
			mval, classerr := coder.toMapClass(st.(reflectx.IRefObject), nil, 1)
			if classerr != nil {
				err = classerr
				return
			}
			marr[i] = mval
		case *schemax.BasicType:
			oval, _ := reftype.RefSliceGet(obj, fname, i)
			marr[i] = coder.toMapConvert(etyp, oval)
		case *schemax.EnumType:
			marr[i], _ = reftype.RefSliceGet(obj, fname, i)
		case *schemax.AnyType:
			st, _ := reftype.RefSliceGet(obj, fname, i)
			rmap, anyerr := coder.toMapAny(st, nil, 1)
			if anyerr != nil {
				err = anyerr
				return
			}
			if rmap == nil {
				continue
			}
			marr[i] = rmap
		default:
			err = errors.New("RefToMap array: not support type: " + etyp.String())
			return
		}
	}

	return
}

func (coder *RefToMap) toMapMap(reftype reflectx.IRefMap, obj reflectx.IRefObject, sany interface{}, fname string, ftyp *schemax.MapType, keyMode int) (rmap map[string]interface{}, err error) {
	_, isNil := reftype.RefGet(obj, fname)
	if isNil {
		return
	}

	if sany == nil {
		rmap = map[string]interface{}{}
	} else {
		rmap = sany.(map[string]interface{})
	}
	keyModeChild := keyMode
	keys := reftype.RefMapKeys(obj, fname)
	for _, key := range keys {
		mval, ok := rmap[key]
		if keyMode == 0 {
			if !ok {
				continue
			}
			if mval == 1 { // 需要处理
				keyModeChild = 1
				mval = nil // 设置为nil
			} else {
				keyModeChild = keyMode
			}
		}

		switch etyp := ftyp.Value.(type) {
		case *schemax.ClassType:
			st, _ := reftype.RefMapGet(obj, fname, key)
			rval, classerr := coder.toMapClass(st.(reflectx.IRefObject), mval, keyModeChild)
			if classerr != nil {
				err = classerr
				return
			}
			rmap[key] = rval
		case *schemax.BasicType:
			oval, _ := reftype.RefMapGet(obj, fname, key)
			rmap[key] = coder.toMapConvert(etyp, oval)
		case *schemax.EnumType:
			rmap[key], _ = reftype.RefMapGet(obj, fname, key)
		default:
			err = errors.New("RefToMap array: not support type: " + etyp.String())
			return

		}
	}

	return
}
